#include "Lighting.hpp"
#include <misc/aerScreenQuad.hpp>

using namespace aer;


void Lighting::init()
{ 
  // =================
  // == Shadow Pass ==
  // =================
  //
  
  // =======================
  // == Accumulation Pass ==
  // =======================
  accumulationPass.width  = 
  accumulationPass.height = 0; //
  
  accumulationPass.color.generate();
  accumulationPass.color.bind();
    accumulationPass.color.setFilters( Texture::NEAREST, Texture::NEAREST);
  accumulationPass.color.unbind();
  
  accumulationPass.framebuffer.generate();
 
 
  aer::Program *pProgram = NULL;
  
  // ==============
  // == DirLight ==
  // ==============
  pProgram = &m_dirLight.accumProgram;
  pProgram->generate();
    pProgram->addShader( Program::VERTEX_SHADER,   "AccumDirLight.Vertex");
    pProgram->addShader( Program::FRAGMENT_SHADER, "AccumDirLight.Fragment");
  AER_CHECK( pProgram->link() );
  
  
  // ================
  // == OmniLights ==
  // ================
  pProgram = &m_omniLight.accumProgram;
  pProgram->generate();
    pProgram->addShader( Program::VERTEX_SHADER,   "AccumOmniLight.Vertex");
    pProgram->addShader( Program::FRAGMENT_SHADER, "AccumOmniLight.Fragment");
  AER_CHECK( pProgram->link() );
  
  
  m_omniLight.positionTB.generate();
  m_omniLight.positionTB.setTarget( DeviceBuffer::TEXTURE );
  //
  
  m_omniLight.colorTB.generate();
  m_omniLight.colorTB.setTarget( DeviceBuffer::TEXTURE );
  //
  
  m_omniLight.mesh.init( 1.0f, 24u);
}

void Lighting::release()
{ 
  // =================
  // == Shadow Pass ==
  // =================
  //
  
  // =======================
  // == Accumulation Pass ==
  // =======================
  accumulationPass.color.release();
  accumulationPass.framebuffer.release();
  
  // ==============
  // == DirLight ==
  // ==============
  m_dirLight.accumProgram.release();  
  
  // ================
  // == OmniLights ==
  // ================
  m_omniLight.accumProgram.release();  
  m_omniLight.positionTB.release();
  m_omniLight.colorTB.release();
}


void Lighting::resize( const int width, const int height)
{
  // =================
  // == Shadow Pass ==
  // =================
  //
  
  // =======================
  // == Accumulation Pass ==
  // =======================
  accumulationPass.width = width;
  accumulationPass.height = height;
  
  accumulationPass.color.bind();
    accumulationPass.color.resize( accumulationPass.width, accumulationPass.height, GL_RGBA8);
  accumulationPass.color.unbind();
  
  accumulationPass.framebuffer.bind();
  {
    FrameBuffer &fbo = accumulationPass.framebuffer;
    fbo.attachColorBuffer( &accumulationPass.color, GL_COLOR_ATTACHMENT0 );
    //fbo.attachSpecialBuffer( &m_gbuffer.depthStencil, GL_DEPTH_STENCIL_ATTACHMENT );
    
    AER_CHECK( FrameBuffer::CheckStatus() );
  }
  accumulationPass.framebuffer.unbind();
  accumulationPass.framebuffer.resize( width, height);//
}

void Lighting::applyShadowmap()
{
}

void Lighting::applyAccumulation(Data &data, Texture *pNormalBuffer, Texture *pDepthBuffer)
{
  Camera *pCamera = data.viewer.camera;
  
  // We need the view matrix to compute lights pos/dir in view-space
  const Matrix4x4 &viewProjMatrix = pCamera->getViewProjectionMatrix();
  const Matrix4x4 &viewMatrix = pCamera->getViewMatrix();
  const Vector2 &linearizationParams = pCamera->getFrustum().getLinearizationParams();
  
  
  accumulationPass.framebuffer.begin();
  {  
    //
    accumulationPass.framebuffer.attachSpecialBuffer( pDepthBuffer, GL_DEPTH_STENCIL_ATTACHMENT);
    //
 
    glViewport( 0, 0, accumulationPass.width, accumulationPass.height);
    glClear( GL_COLOR_BUFFER_BIT );
 
 
    // ===================
    // == Set GL states == 
    // ===================
    // Use additive blending for light accumulation
    glDisable( GL_BLEND );
      glBlendEquation( GL_FUNC_ADD );
      glBlendFunc( GL_ONE, GL_ONE);
    
    // Depth buffer is used but not written into
    glEnable( GL_DEPTH_TEST );
    glDepthMask( GL_FALSE );
  
    glPolygonMode( GL_FRONT_AND_BACK, GL_FILL);
        
        
    // ==================
    // == Bind buffers ==
    // ==================
		pNormalBuffer->bind( 0 );
		pDepthBuffer->bind( 1 );
		
    
#if 1
    // ==============
    // == DirLight ==
    // ==============
    
    glDisable( GL_STENCIL_TEST );
    glDisable( GL_CULL_FACE );
    
    m_dirLight.accumProgram.bind();
    {
      m_dirLight.accumProgram.setUniform( "uNormalBuffer", 0);
      m_dirLight.accumProgram.setUniform( "uDepthBuffer",  1);
      //m_dirLight.accumProgram.setUniform( "uLightColor", data.dirlight.color);
      //m_dirLight.accumProgram.setUniform( "uLightDirVS", data.dirlight.direction);
    
      Vector3 lightDir( 0.25f, 0.25f, 1.0f); 
      lightDir = Matrix3x3(viewMatrix) * lightDir;
      m_dirLight.accumProgram.setUniform( "uLightDirVS", lightDir);
            
      ScreenQuad::Draw();
    }
    m_dirLight.accumProgram.unbind();
#endif
        
#if 1
    // ================
    // == OmniLights ==
    // ================
    
    glEnable( GL_STENCIL_TEST );
    
    glEnable( GL_CULL_FACE );
      glCullFace( GL_FRONT );
    
		m_omniLight.accumProgram.bind();
		{
      aer::Program &program = m_omniLight.accumProgram;
      
      //m_omniLight.positionTB.bind(..);
      //m_omniLight.colorTB.bind(..);
      //program.setUniform( "uViewProjMatrix", .. );
      //program.setUniform( "uInvViewProjMatrix", .. );
      
      Vector3 lightPos = Vector3( 50.0f, 15.0f, -50.0f);
      float lightRadius = 70.0f;
      
      Matrix4x4 model = glm::translate( glm::mat4(1.0f), lightPos );//
      lightPos = Vector3( viewMatrix * Vector4( lightPos, 1.0f) );
      
      program.setUniform( "uDebugLight", Vector4( lightPos, lightRadius));//
      program.setUniform( "uLightColor", 0.7f, 0.2f, 0.0f);//
      
      program.setUniform( "uModelViewProjMatrix", viewProjMatrix * model);
      program.setUniform( "uModelViewMatrix",     viewMatrix * model);
      
      program.setUniform( "uNormalBuffer", 0);
      program.setUniform( "uDepthBuffer",  1);
      program.setUniform( "uLinearParams", linearizationParams);
      
      
      glClear( GL_STENCIL_BUFFER_BIT );
      
      /// Pass 1
      glColorMask( GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
      
      glDepthFunc( GL_GEQUAL );
      glCullFace( GL_FRONT );
      
      glStencilFunc( GL_ALWAYS, 1, 0xff);
      glStencilOp( GL_KEEP, GL_KEEP, GL_REPLACE);
      
      m_omniLight.mesh.draw();
      
      /// Pass 2
      glColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
      
      if (true /*cameraInsideShape*/) {
        glDepthFunc( GL_ALWAYS );
      } else {       
        glDepthFunc( GL_LEQUAL );
        glCullFace( GL_BACK ); 
      }
      
      glStencilFunc( GL_EQUAL, 1, 0xff);
      glStencilOp( GL_KEEP, GL_KEEP, GL_KEEP);
      
      glEnable( GL_BLEND );      
        m_omniLight.mesh.draw();      
      glDisable( GL_BLEND );
    }
    m_omniLight.accumProgram.unbind();
#endif

    // ====================
    // == Unbind buffers ==
		// ====================
    pNormalBuffer->unbind();
    pDepthBuffer->unbind();
    Texture::Activate(0);
    
    
    // =====================
    // == Unset GL states ==
    // =====================
		glDisable(GL_BLEND);
    
    glEnable( GL_DEPTH_TEST );
      glDepthFunc( GL_LEQUAL );      
    glDepthMask( GL_TRUE );  
  
    glDisable( GL_STENCIL_TEST );
  
    glDisable( GL_CULL_FACE );
      glCullFace( GL_BACK );
  }
  accumulationPass.framebuffer.end();
}
